<?php

/*
 * This file is part of jwt-auth.
 *
 * (c) Sean Tymon <tymon148@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Trip\Auth\JWT\Providers;

use App\Trip\Auth\JWT\Blacklist;
use App\Trip\Auth\JWT\Claims\Factory as ClaimFactory;
use App\Trip\Auth\JWT\Console\JWTGenerateSecretCommand;
use App\Trip\Auth\JWT\Contracts\Providers\Auth;
use App\Trip\Auth\JWT\Contracts\Providers\JWT as JWTContract;
use App\Trip\Auth\JWT\Contracts\Providers\Storage;
use App\Trip\Auth\JWT\Factory;
use App\Trip\Auth\JWT\Http\Parser\AuthHeaders;
use App\Trip\Auth\JWT\Http\Parser\InputSource;
use App\Trip\Auth\JWT\Http\Parser\Parser;
use App\Trip\Auth\JWT\Http\Parser\QueryString;
use App\Trip\Auth\JWT\JWT;
use App\Trip\Auth\JWT\JWTGuard;
use App\Trip\Auth\JWT\Manager;
use App\Trip\Auth\JWT\Providers\JWT\Lcobucci;
use App\Trip\Auth\JWT\Validators\PayloadValidator;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\ServiceProvider;


abstract class AbstractServiceProvider extends ServiceProvider
{
    /**
     * The middleware aliases.
     *
     * @var array
     */
    protected array $middlewareAliases
        = [

        ];

    /**
     * Boot the service provider.
     *
     * @return void
     */
    abstract public function boot(): void;

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register(): void
    {
        // 别名注册
        $this->registerAliases();

        // 注册jwt/jwt.auth/jwt.storage服务
        $this->registerJWTProvider();
        $this->registerAuthProvider();
        $this->registerStorageProvider();

        // 注册 黑名单，管理器，token解析器
        $this->registerJWTBlacklist();
        $this->registerManager();
        $this->registerTokenParser();

        $this->registerJWT();
        $this->registerPayloadValidator();
        $this->registerClaimFactory();
        $this->registerPayloadFactory();
        $this->registerJWTCommand();

        // command
        $this->commands('jwt.secret');
    }

    /**
     * Bind some aliases.
     *
     * @return void
     */
    protected function registerAliases(): void
    {
        $this->app->alias('jwt', JWT::class);
        $this->app->alias('jwt.provider.jwt', JWTContract::class);
        $this->app->alias('jwt.provider.jwt.lcobucci', Lcobucci::class);
        $this->app->alias('jwt.provider.auth', Auth::class);
        $this->app->alias('jwt.provider.storage', Storage::class);
        $this->app->alias('jwt.manager', Manager::class);
        $this->app->alias('jwt.blacklist', Blacklist::class);
        $this->app->alias('jwt.payload.factory', Factory::class);
        $this->app->alias('jwt.validators.payload', PayloadValidator::class);
    }

    /**
     * Register the bindings for the JSON Web Token provider.
     *
     * @return void
     */
    protected function registerJWTProvider(): void
    {
        $this->registerLcobucciProvider();
        $this->app->singleton('jwt.provider.jwt', function ($app) {
            return $this->getConfigInstance('providers.jwt');
        });
    }

    /**
     * Register the bindings for the Lcobucci JWT provider.
     *
     * @return void
     */
    protected function registerLcobucciProvider(): void
    {
        $this->app->singleton('jwt.provider.jwt.lcobucci', function ($app) {
            return new Lcobucci(
                $this->config('secret'),
                $this->config('algo'),
                $this->config('keys')
            );
        });
    }

    /**
     * Helper to get the config values.
     *
     * @param string $key
     * @param string $default
     *
     * @return mixed
     */
    protected function config(string $key, $default = null)
    {
        return config("jwt.$key", $default);
    }

    /**
     * Get an instantiable configuration instance.
     *
     * @param string $key
     *
     * @return mixed
     * @throws BindingResolutionException
     */
    protected function getConfigInstance(string $key)
    {
        $instance = $this->config($key);

        if (is_string($instance)) {
            return $this->app->make($instance);
        }

        return $instance;
    }

    /**
     * Register the bindings for the Auth provider.
     *
     * @return void
     */
    protected function registerAuthProvider(): void
    {
        $this->app->singleton('jwt.provider.auth', function () {
            return $this->getConfigInstance('providers.auth');
        });
    }

    /**
     * Register the bindings for the Storage provider.
     *
     * @return void
     */
    protected function registerStorageProvider(): void
    {
        $this->app->singleton('jwt.provider.storage', function () {
            return $this->getConfigInstance('providers.storage');
        });
    }

    /**
     * Register the bindings for the Blacklist.
     *
     * @return void
     */
    protected function registerJWTBlacklist(): void
    {
        $this->app->singleton('jwt.blacklist', function ($app) {
            $instance = new Blacklist($app['jwt.provider.storage']);

            return $instance->setGracePeriod($this->config('blacklist_grace_period'))
                            ->setRefreshTTL($this->config('refresh_ttl'));
        });
    }

    /**
     * Register the bindings for the JWT Manager.
     *
     * @return void
     */
    protected function registerManager(): void
    {
        $this->app->singleton('jwt.manager', function ($app) {
            $instance = new Manager(
                $app['jwt.provider.jwt'],
                $app['jwt.blacklist'],
                $app['jwt.payload.factory']
            );

            return $instance->setBlacklistEnabled((bool)$this->config('blacklist_enabled'))
                            ->setPersistentClaims($this->config('persistent_claims'));
        });
    }

    /**
     * Register the bindings for the Token Parser.
     *
     * @return void
     */
    protected function registerTokenParser(): void
    {
        $this->app->singleton('jwt.parser', function ($app) {
            $parser = new Parser(
                $app['request'],
                [
                    new AuthHeaders,
                    new QueryString,
                    new InputSource,
                ]
            );

            $app->refresh('request', $parser, 'setRequest');

            return $parser;
        });
    }

    /**
     * Register the bindings for the main JWT class.
     *
     * @return void
     */
    protected function registerJWT(): void
    {
        $this->app->singleton('jwt', function ($app) {
            return (new JWT(
                $app['jwt.manager'],
                $app['jwt.parser']
            ))->lockSubject($this->config('lock_subject'));
        });
    }


    /**
     * Register the bindings for the payload validator.
     *
     * @return void
     */
    protected function registerPayloadValidator(): void
    {
        $this->app->singleton('jwt.validators.payload', function () {
            return (new PayloadValidator)
                ->setRefreshTTL($this->config('refresh_ttl'))
                ->setRequiredClaims($this->config('required_claims'));
        });
    }

    /**
     * Register the bindings for the Claim Factory.
     *
     * @return void
     */
    protected function registerClaimFactory(): void
    {
        $this->app->singleton('jwt.claim.factory', function ($app) {
            $factory = new ClaimFactory($app['request']);
            $app->refresh('request', $factory, 'setRequest');

            return $factory->setTTL($this->config('ttl'))
                           ->setLeeway($this->config('leeway'));
        });
    }

    /**
     * Register the bindings for the Payload Factory.
     *
     * @return void
     */
    protected function registerPayloadFactory(): void
    {
        $this->app->singleton('jwt.payload.factory', function ($app) {
            return new Factory(
                $app['jwt.claim.factory'],
                $app['jwt.validators.payload']
            );
        });
    }

    /**
     * Register the Artisan command.
     *
     * @return void
     */
    protected function registerJWTCommand(): void
    {
        $this->app->singleton('jwt.secret', function () {
            return new JWTGenerateSecretCommand;
        });
    }

    /**
     * Extend Laravel's Auth.
     *
     * @return void
     */
    protected function extendAuthGuard(): void
    {
        // 拓展授权
        $this->app['auth']->extend('jwtoken', function ($app, $name, array $config) {
            $guard = new JWTGuard(
                $app['jwt'],
                $app['auth']->createUserProvider($config['provider']),
                $app['request']
            );

            $app->refresh('request', $guard, 'setRequest');
            return $guard;
        });
    }
}
